package com.learn.springboot.controller.dispersed;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author wangxing
 * @version 2020/8/7 15:50 Administrator
 */
@RestController
public class SellProController {
    @Autowired
    Redisson redisson;
    @Autowired
    JedisPool jedisPool;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_stock/no_lock")
    public String deductStock() {
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        synchronized (this) {
            if (stock > 0) {
                Integer new_stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
            } else {
                return " deduct stock 失败 ";
            }
        }
    }

    private static final String lockKey = "productLockKey";

    /**
     * 不设置超时时间,出现线程中断,导致上锁不解锁,线程死锁
     *
     * @return
     */
    @RequestMapping("/deduct_stock/lock0")
    public String deductStockLock0() {
//        分布式锁 设置超时时间 10秒如果没处理完也放弃锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
/**
 * 分布式锁涉及的问题
 * 分布式锁死锁
 * 1.不设置超时时间,出现线程中断,导致上锁不解锁,线程死锁
 * 2.设置锁时间和添加锁不是原子性的,出现设置锁超时未生效
 * 分布式锁失败的可能性
 * 1.锁被其他线程操作删除
 *  原因:业务执行时间超过了
 *
 */
        if (!result) {
            return "{lock}";
        }

        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        try {
            synchronized (this) {
                if (stock > 0) {
                    Integer new_stock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                    return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
                }
            }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return " deduct stock 失败 ";
    }

    /**
     * 设置锁时间和添加锁不是原子性的,出现设置锁超时未生效可能性
     *
     * @return
     */
    @RequestMapping("/deduct_stock/lock1")
    public String deductStockLock1() {
//        分布式锁 设置超时时间 10秒如果没处理完也放弃锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        if (true) {
            int a = 1 / 0;
        }
        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
/**
 * 分布式锁涉及的问题
 * 分布式锁死锁
 * 1.不设置超时时间,出现线程中断,导致上锁不解锁,线程死锁
 * 2.设置锁时间和添加锁不是原子性的,出现设置锁超时未生效
 * 分布式锁失败的可能性
 * 1.锁被其他线程操作删除
 *  原因:业务执行时间超过了
 *
 */
        if (!result) {
            return "{lock}";
        }

        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        try {
            synchronized (this) {
                if (stock > 0) {
                    Integer new_stock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                    return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
                }
            }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return " deduct stock 失败 ";
    }

    /**
     * 业务逻辑执行时间过长导致超时,锁自动释放,而后当前业务逻辑执行完成,会删除下一个业务逻辑的分布式锁,导致后续线程锁失效
     *
     * @return
     */
    @RequestMapping("/deduct_stock/lock2")
    public String deductStockLock2() {
//        分布式锁 设置超时时间 10秒如果没处理完也放弃锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
/**
 * 分布式锁涉及的问题
 * 分布式锁死锁
 * 1.不设置超时时间,出现线程中断,导致上锁不解锁,线程死锁
 * 2.设置锁时间和添加锁不是原子性的,出现设置锁超时未生效
 * 分布式锁失败的可能性
 * 1.锁被其他线程操作删除
 *  原因:业务执行时间超过了
 *
 */
        if (!result) {
            return "{lock}";
        }

        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        try {
            //暂停15秒
            Thread.sleep(15000);
            synchronized (this) {

                if (stock > 0) {
                    Integer new_stock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                    return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return " deduct stock 失败 ";
    }

    /**
     * 给每个方法添加唯一的一个标识,当进行删除时进行判定,防止删除错误
     *
     * @return
     */
    @RequestMapping("/deduct_stock/lock3")
    public String deductStockLock3() {
        String lockValue = UUID.randomUUID().toString();
//        分布式锁 设置超时时间 10秒如果没处理完也放弃锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
/**
 * 分布式锁涉及的问题
 * 分布式锁死锁
 * 1.不设置超时时间,出现线程中断,导致上锁不解锁,线程死锁
 * 2.设置锁时间和添加锁不是原子性的,出现设置锁超时未生效
 * 分布式锁失败的可能性
 * 1.锁被其他线程操作删除
 *  原因:业务执行时间超过了
 */
        if (!result) {
            return "{lock}";
        }

        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        try {
            synchronized (this) {
                if (stock > 0) {
                    Integer new_stock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                    return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
                }
            }
        } finally {
            //只有是当前锁的进程才能删除当前锁
            if (lockValue.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return " deduct stock 失败 ";
    }

    /**
     * 添加锁续命功能,使用redisson提供的实现,对当前的业务进行判定,如果还没执行完,则延期锁的销毁时间
     *
     * @return
     */
    @RequestMapping("/deduct_stock/lock4")
    public String deductStockLock4() {
//        内部自定义了包括锁超时,锁续命等相关功能
        RLock lock = redisson.getLock(lockKey);
//        加锁
        lock.lock();
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        try {
            if (stock > 0) {
                Integer new_stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", new_stock.toString());
                return "{old_stock :" + stock + ",new_stock:" + new_stock + "}";
            }
        } finally {
//            解锁
            lock.unlock();
        }
        return " deduct stock 失败 ";
    }

    /**
     * 使用Lua脚本实现商品减库存操作
     * 由于redis是单线程的,所以这个操作是原子的
     *
     * @return
     */
    @RequestMapping("/deduct_stock/Lua")
    public String deductStockLua() {
//        lua命令要尽量简单,因为redis是单线程的,如果lua脚本太长会导致整个redis阻塞,尽量少用lua
        String luaScript = " local count = redis.call('get',KEYS[1])"
                + " local a = tonumber(count) "
                + " local b = tonumber(ARGV[1])"
                + " if a > b then "
                + "   redis.call('set',KEYS[1],a-b)"
                + "  return 1"
                + "  end "
                + "  return 0 ";
        Jedis jedis = jedisPool.getResource();
        Object result = jedis.eval(luaScript, Arrays.asList(lockKey),Arrays.asList("1"));
        if(result.equals(1)){
            return " deduct stock 成功";
        }
        return " deduct stock 失败 ";
    }
}
